package com.ny.nybase.widget.ptr;

import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;

import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;

/**
 * Created by LiangYu on 2018/1/8.
 */
public class PullToRefreshGestureHandler implements GestureDetector.OnGestureListener, GestureHandler {

    private static final String TAG = PullToRefreshListView.class.getSimpleName();
    public static final float DAMP = 2.5f;
    public static final float FOOTER_DAMP = 2.5f;
    public static final int PULL_HEIGHT = 50;
    public static final int SCROLL_DURATION = 300;
    private AbstractPullToRefreshListView listView;
    private PullToRefreshState state = PullToRefreshState.Normal;
    private GestureDetector gestureDetector;
    private Scroller scroller;
    private PullToRefreshWidgetStateStore headerStateStore = new PullToRefreshWidgetStateStore();
    private PullToRefreshWidgetStateStore footerStateStore = new PullToRefreshWidgetStateStore();
    private int curScrollDirection;
    private MotionEvent lastDown = null;
    private MotionEvent lastMove = null;
    private boolean slopExceeded = false;
    private int touchSlop = ViewConfiguration.getTouchSlop();

    public PullToRefreshGestureHandler(AbstractPullToRefreshListView listView){
        this.listView = listView;
        gestureDetector = new GestureDetector(listView.getContext(), this, listView.getHandler());
        scroller = new Scroller(listView.getContext(), new DecelerateInterpolator());
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (e1 == null || e2 == null){
            log("onScroll e1 or e2 is null" + e1 + " " + e2);
            return false;
        }

        switch (state){
            case Normal:{
                onNormal(e1, e2, distanceX, distanceY);
            }
            break;
            case HeaderPulling:{
                onHeaderPulling(e1,e2,distanceX,distanceY);
            }
            break;
            case HeaderScrollBack:
            case HeaderRefreshing:{
                //gotoHeaderExtraPull(e2);
            }
            break;
            case HeaderExtraPull:{
                float dh = e2.getRawY() - headerStateStore.getPullY();
                float exh = headerStateStore.getExtraHeight();
                float damped = dh / DAMP + exh;
                log("HeaderExtraPull dy = " + distanceY + " dh = " + dh + " exh = " + exh + " damped = " + damped);
                setHeaderHeight((int)damped, true);
                if (damped < 0){
                    updateState(PullToRefreshState.Normal);
                    onNormal(e1, e2, distanceX, distanceY);
                }
            }
            break;
            case FooterPulling:{
                onFooterPulling(e1,e2,distanceX,distanceY);
            }
            break;
            case FooterScrollBack:
            case FooterRefreshing:{
                //gotoFooterExtraPull(e2);
            }
            break;
            case FooterExtraPull:{
                float dh = footerStateStore.getPullY() - e2.getRawY();
                float exh = footerStateStore.getExtraHeight();
                float damped = dh / FOOTER_DAMP + exh;
                log("FooterExtraPull dy = " + distanceY + " dh = " + dh + " exh = " + exh + " damped = " + damped);
                setFooterHeight((int) damped, true);
                if (damped < footerStateStore.getInitialHeight()){
                    updateState(PullToRefreshState.Normal);
                    onNormal(e1, e2, distanceX, distanceY);
                }
            }
            break;
        }
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        switch (state){
            /*case HeaderPulling:
            case HeaderExtraPull:{
                if (velocityY < 0){
                    int mh = listView.getHeader().getMeasuredHeight();
                    if (mh > 0) {
                        int duration = (int) ((float)mh / Math.abs(velocityY) * 1000.0f) ;
                        headerScrollToNormal(duration);
                    }
                    return true;
                }
            }
            break;
            case FooterPulling:
            case FooterExtraPull:{
                if (velocityY > 0){
                    int mh = listView.getFooter().getMeasuredHeight();
                    if (mh > 0) {
                        int duration = (int) ((float)mh / Math.abs(velocityY) * 1000.0f) ;
                        footerScrollToNormal(duration);
                    }
                    return true;
                }
            }
            break;*/
        }
        return false;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        PullToRefreshState initialState = state;
        PullToRefreshWidget header = listView.getHeader();
        PullToRefreshWidget footer = listView.getFooter();

        // 不支持多点触控
        int action = ev.getActionMasked();
        int index = ev.getActionIndex();
        /*if (index != 0){
            return true;
        }*/

        //log("onTouchEvent "+ev.getActionIndex()+">[+ev.getActionMasked()+] ry = "+ev.getRawY()+", y = "+ev.getY());

        boolean result = false;
        if (action != ACTION_DOWN){
            //result = gestureDetector.onTouchEvent(ev);
        }

        if (!result){
            switch (action){
                case ACTION_CANCEL:
                case ACTION_UP: {
                    switch (state){
                        case HeaderPulling:{
                            int mh = header.getMeasuredHeight();
                            int pullHeight = getPullHeight();

                            // 不在顶部时不滚回起始位置
                            if (listView.getFirstVisiblePosition() == 0){
                                boolean scrollToRefresh = false;
                                if (mh > pullHeight){
                                    if (listView.getHeaderRefreshEnabled()){
                                        scrollToRefresh = true;
                                    }
                                }

                                if (scrollToRefresh) {
                                    headerScrollToRefreshing();
                                    updateState(PullToRefreshState.HeaderRefreshing);
                                }else{
                                    headerScrollToNormal(SCROLL_DURATION);
                                }
                            }else{
                                updateState(PullToRefreshState.Normal);
                            }
                        }
                        break;
                        case HeaderExtraPull:{
                            headerScrollToNormal(SCROLL_DURATION);
                        }
                        break;
                        case FooterPulling:{
                            int mh = footer.getMeasuredHeight();
                            int pullHeight = getPullHeight();

                            // 不在底部时不滚回起始位置
                            if (listView.getLastVisiblePosition() == listView.getCount() - 1){
                                boolean scrollToRefresh = false;
                                if (mh > pullHeight){
                                    if (listView.getFooterRefreshEnabled()){
                                        scrollToRefresh = true;
                                    }
                                }

                                if (scrollToRefresh) {
                                    footerScrollToRefreshing();
                                    updateState(PullToRefreshState.FooterRefreshing);
                                }else{
                                    footerScrollToNormal(SCROLL_DURATION);
                                }
                            }else{
                                updateState(PullToRefreshState.Normal);
                            }
                        }
                        break;
                        case FooterExtraPull:{
                            footerScrollToNormal(SCROLL_DURATION);
                        }
                        break;
                    }
                }
                break;
                case ACTION_DOWN:{
                    switch (state){
                        case HeaderScrollBack:
                        case HeaderRefreshing:
                            gotoHeaderExtraPull(ev);
                            break;
                        case FooterScrollBack:
                        case FooterRefreshing:
                            gotoFooterExtraPull(ev);
                            break;
                    }
                }
                    break;
                case ACTION_MOVE:{
                    // 达到slop一次后面就算作滚动，解决下拉和上拉切换的时候停顿的问题
                    log("ACTION_MOVE slopExceeded = " + slopExceeded);
                    if (!slopExceeded){
                        float dx = ev.getRawX() - lastDown.getRawX();
                        float dy = ev.getRawY() - lastDown.getRawY();
                        if (dx * dx + dy * dy > touchSlop * touchSlop){
                            slopExceeded = true;
                        }
                    }

                    if (slopExceeded){
                        MotionEvent lastMove = (this.lastMove != null) ? this.lastMove : lastDown;
                        onScroll(lastDown, ev, ev.getRawX() - lastMove.getRawX(), ev.getRawY() - lastMove.getRawY());
                    }

                    lastMove = MotionEvent.obtain(ev);
                }
                    break;
                case ACTION_POINTER_DOWN:
                case ACTION_POINTER_UP:
                    result = false;
                    break;
            }
        }

        // 刷新中拦截所有事件
        switch (initialState){
            case HeaderRefreshing:
            case FooterRefreshing:
                result = true;
        }

        return result;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == ACTION_DOWN){
            lastDown = MotionEvent.obtain(ev);
            lastMove = null;
            slopExceeded = false;
            gestureDetector.onTouchEvent(ev);
        }
        switch (state){
            // 正在刷新不可点击
            case FooterRefreshing:
            case HeaderRefreshing:{
                return true;
            }
        }
        return false;
    }

    private int getPullHeight(){
        return PullToRefreshUtil.dip2px(listView.getContext(), PULL_HEIGHT);
    }

    private int getFooterHeight(){
        return listView.getFooterHeight();
    }

    public void updateState(PullToRefreshState state){
        log("updateState => " + state);
        this.state = state;

        switch (state){
            case Normal:{
                setFooterState(PullToRefreshWidgetState.PullToRefresh);
                setHeaderState(PullToRefreshWidgetState.PullToRefresh);
                positionReset();
            }
            break;
            case HeaderRefreshing:{
                listView.dispatchHeaderRefresh();
                setHeaderState(PullToRefreshWidgetState.Refreshing);
            }
            break;
            case FooterRefreshing:{
                listView.dispatchFooterRefresh();
                setFooterState(PullToRefreshWidgetState.Refreshing);
            }
            break;
            case HeaderScrollBack:{
                setHeaderState(PullToRefreshWidgetState.Finished);
            }
            break;
            case FooterScrollBack:{
                setFooterState(PullToRefreshWidgetState.Finished);
            }
            break;
            case HeaderExtraPull:{
                setHeaderState(PullToRefreshWidgetState.Finished);
            }
            break;
            case FooterExtraPull:{
                setFooterState(PullToRefreshWidgetState.Finished);
            }
            break;
        }
    }

    private void headerScroll(int dstHeight, int duration){
        int mh = listView.getHeader().getMeasuredHeight();
        if (duration > SCROLL_DURATION || duration <= 0){
            duration = SCROLL_DURATION;
        }else if (dstHeight == mh){
            duration = 1;
        }else if (Math.abs(dstHeight - mh) < 10){
            duration = 100;
        }

        curScrollDirection = 1;
        scroller.startScroll(0, mh, 0, -mh+dstHeight, duration);
        listView.invalidate();
    }

    private void headerScrollToRefreshing(){
        headerScroll(getPullHeight(), SCROLL_DURATION);
    }

    private void headerScrollToNormal(int duration){
        updateState(PullToRefreshState.HeaderScrollBack);
        headerScroll(0, duration);
    }

    private void footerScroll(int dstHeight, int duration){
        int mh = listView.getFooter().getMeasuredHeight();
        if (duration > SCROLL_DURATION || duration <= 0){
            duration = SCROLL_DURATION;
        }else if (dstHeight == mh){
            duration = 1;
        }else if (Math.abs(dstHeight - mh) < 10){
            duration = 100;
        }

        curScrollDirection = 2;
        scroller.startScroll(0, mh, 0, -mh+dstHeight, duration);
        listView.invalidate();
    }

    private void footerScrollToRefreshing(){
        footerScroll(getFooterHeight(), SCROLL_DURATION);
    }

    private void footerScrollToNormal(int duration){
        updateState(PullToRefreshState.FooterScrollBack);
        //footerScroll(getFooterHeight(), duration);
        footerScroll((int) footerStateStore.getInitialHeight(), duration);
    }

    @Override
    public void computeScroll() {
        if (!scroller.computeScrollOffset()){
            return;
        }
        int y = scroller.getCurrY();
        log("computeScroll y = " + y);
        if (curScrollDirection == 1){
            setHeaderHeight(y, false);
            // 滚回来了，状态变为正常
            if (state == PullToRefreshState.HeaderScrollBack){
                if (y == 0){
                    updateState(PullToRefreshState.Normal);
                }
            }
        }else if (curScrollDirection == 2){
            setFooterHeight(y, false);
            if (state == PullToRefreshState.FooterScrollBack){
                if (y == footerStateStore.getInitialHeight()){
                    updateState(PullToRefreshState.Normal);
                }
            }
        }
    }

    @Override
    public void stopHeaderRefresh(boolean anim){
        if (state == PullToRefreshState.HeaderRefreshing){
            if (anim){
                headerScrollToNormal(SCROLL_DURATION);
            }else{
                scroller.forceFinished(true);
                updateState(PullToRefreshState.Normal);
                setHeaderHeight(0, false);
            }
        }
    }

    @Override
    public void stopFooterRefresh(boolean anim) {
        if (state == PullToRefreshState.FooterRefreshing){
            if (anim){
                footerScrollToNormal(SCROLL_DURATION);
            }else{
                scroller.forceFinished(true);
                updateState(PullToRefreshState.Normal);
                setFooterHeight(getFooterHeight(), false);
            }
        }
    }

    @Override
    public void resetFooter() {
        scroller.forceFinished(true);
        updateState(PullToRefreshState.Normal);
        setFooterHeight(getFooterHeight(), false);
    }

    @Override
    public void startHeaderRefresh() {
        if (listView != null){
            if (listView.getHeaderRefreshEnabled()){
                if (state == PullToRefreshState.Normal){
                    updateState(PullToRefreshState.HeaderRefreshing);
                    headerScrollToRefreshing();
                }
            }
        }
    }

    @Override
    public void startFooterRefresh() {
        if (listView != null){
            if (listView.getFooterRefreshEnabled()){
                if (state == PullToRefreshState.Normal){
                    updateState(PullToRefreshState.FooterRefreshing);
                    footerScrollToRefreshing();
                }
            }
        }
    }

    private void setHeaderState(PullToRefreshWidgetState state){
        listView.setHeaderState(state);
    }

    private void setFooterState(PullToRefreshWidgetState state){
        listView.setFooterState(state);
    }

    private void setHeaderHeight(int px, boolean fixed){
        PullToRefreshWidget header = listView.getHeader();
        int h = px;
        if (h < 0){
            h = 0;
        }
        header.setHeight(h);
        log("setHeaderHeight px = " + px + " mh = " + header.getMeasuredHeight());
        if (fixed){
            if (px > 0){
                listView.setSelectionFromTop(0, 0);
            }
        }
    }

    private void setFooterHeight(int px, boolean fixed){
        PullToRefreshWidget footer = listView.getFooter();

        int mh = footer.getMeasuredHeight();
        int footerInitialHeight = getFooterHeight();

        int h = px;
        if (h < footerInitialHeight){
            h = footerInitialHeight;
        }
        log("setFooterHeight px = " + px + " mh = " + mh);
        footer.setHeight(h);
        if (fixed){
            if (px > footerInitialHeight){
                listView.setSelectionFromTop(listView.getCount() - 1, 0);
            }
        }
    }

    private void log(String content){
        if (PullToRefreshUtil.isLogEnabled()){
            Log.i(TAG, content);
        }
    }

    private void onHeaderPulling(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
        // 拖动距离
        float dh = e2.getRawY() - headerStateStore.getPullY();
        // 阻尼目标距离
        float damped = dh / DAMP;
        log("onScroll onHeaderPulling "
                + " dy = " + distanceY
                + " dh = " + dh
                + " extraHeight = " + footerStateStore.getExtraHeight()
                + " damped = " + damped
        );
        setHeaderHeight((int)damped, true);
        if (damped < 0){
            updateState(PullToRefreshState.Normal);
            onNormal(e1, e2, distanceX, distanceY);
        }else{
            setHeaderState(damped > getPullHeight() ? PullToRefreshWidgetState.ReleaseToRefresh : PullToRefreshWidgetState.PullToRefresh);
        }
    }

    private void onFooterPulling(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
        // 拖动距离
        float dh = - (e2.getRawY() - footerStateStore.getPullY());
        // 阻尼目标距离
        float damped = dh / FOOTER_DAMP + footerStateStore.getExtraHeight();
        setFooterHeight((int) damped, true);
        log("onScroll onFooterPulling "
                + " dy = " + distanceY
                + " dh = " + dh
                + " extraHeight = " + footerStateStore.getExtraHeight()
                + " damped = " + damped
        );

        if (damped < footerStateStore.getInitialHeight()){
            updateState(PullToRefreshState.Normal);
            onNormal(e1, e2, distanceX, distanceY);
        }else{
            setFooterState(damped > getPullHeight() ? PullToRefreshWidgetState.ReleaseToRefresh : PullToRefreshWidgetState.PullToRefresh);
        }
    }

    private void onNormal(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
        PullToRefreshWidget header = listView.getHeader();
        PullToRefreshWidget footer = listView.getFooter();
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        int lastVisiblePosition = listView.getLastVisiblePosition();
        int viewCount = listView.getCount();
        int headerTop = ((View)header).getTop();
        int footerBottom = ((View)footer).getBottom();
        int listViewBottom = listView.getHeight() - listView.getPaddingBottom();
        log("onNormal y1=" + e1.getRawY() + " y2=" + e2.getRawY() + " dy=" + distanceY);

        if (positionReset()){
            return;
        }

        if (firstVisiblePosition == 0 && distanceY > 0 && headerTop == listView.getPaddingTop()){
            updateState(PullToRefreshState.HeaderPulling);
            float pullY = e2.getRawY();
            headerStateStore.setPullY(pullY);
            headerStateStore.setExtraHeight(0);
            headerStateStore.setInitialHeight(0);
            onHeaderPulling(e1,e2,distanceX,distanceY);
        }else if (lastVisiblePosition == viewCount - 1 && distanceY < 0){
            if (footerBottom == listViewBottom){
                updateState(PullToRefreshState.FooterPulling);
                float pullY = e2.getRawY();
                footerStateStore.setPullY(pullY);
                float extraHeight = footer.getMeasuredHeight();
                footerStateStore.setExtraHeight(extraHeight);
                footerStateStore.setInitialHeight(extraHeight);
                onFooterPulling(e1,e2,distanceX,distanceY);
            }else if (footerBottom < listViewBottom && headerTop == 0){
                int h = footer.getMeasuredHeight() + listViewBottom - footerBottom;
                //setFooterHeight(h, true);

                updateState(PullToRefreshState.FooterPulling);
                float pullY = e2.getRawY();
                footerStateStore.setPullY(pullY);
                footerStateStore.setExtraHeight(h);
                footerStateStore.setInitialHeight(h);
                onFooterPulling(e1,e2,distanceX,distanceY);
            }
        }
    }

    private void gotoHeaderExtraPull(MotionEvent ev){
        // 如果看不见，不执行动作
        if (listView.getFirstVisiblePosition() != 0){
            return;
        }

        // 强制停止：防止和PullWhenRefreshing结束的滚动冲突。
        // Refreshing状态时，如果还没有滑到指定位置，此时迅速拖动并松开，
        // 会立即进入PullWhenRefreshing状态并迅速结束，触发scroll，
        // 和滚动到Refreshing位置的scroll冲突，导致闪动
        scroller.forceFinished(true);
        updateState(PullToRefreshState.HeaderExtraPull);
        float pullY = ev.getRawY();
        float extraHeight = listView.getHeader().getMeasuredHeight();
        headerStateStore.setPullY(pullY);
        headerStateStore.setExtraHeight(extraHeight);
    }

    private void gotoFooterExtraPull(MotionEvent ev){
        // 如果看不见，不执行动作
        if (listView.getLastVisiblePosition() != listView.getCount() - 1){
            return;
        }

        // 强制停止：防止和PullWhenRefreshing结束的滚动冲突。
        // Refreshing状态时，如果还没有滑到指定位置，此时迅速拖动并松开，
        // 会立即进入PullWhenRefreshing状态并迅速结束，触发scroll，
        // 和滚动到Refreshing位置的scroll冲突，导致闪动
        scroller.forceFinished(true);
        updateState(PullToRefreshState.FooterExtraPull);
        float pullY = ev.getRawY();
        float extraHeight = listView.getFooter().getMeasuredHeight();
        footerStateStore.setPullY(pullY);
        footerStateStore.setExtraHeight(extraHeight);
        log("pullY = " + pullY + " extraHeight = " + extraHeight);
    }

    /**
     * 位置重新调整。
     * 正在拖动时，如果突然增加一个footer，会导致最终位置计算不准确，
     * 结果就是本来一屏就能显示完了，结果还能向上划一点
     *
     * @return 位置是否需要重置
     */
    private boolean positionReset(){
        PullToRefreshWidget header = listView.getHeader();
        PullToRefreshWidget footer = listView.getFooter();
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        int headerTop = ((View)header).getTop();
        int listPaddingTop = listView.getPaddingTop();
        if (footer.getMeasuredHeight() > listView.getFooterHeight()){
            if (firstVisiblePosition == 0 && headerTop != listPaddingTop || firstVisiblePosition != 0){
                setFooterHeight(listView.getFooterHeight(), false);
                return true;
            }
        }
        return false;
    }
}
